// Functions for recording & replaying input
#include "burner.h"
#include "dynhuff.h"
#include <commdlg.h>

#define MAX_METADATA 1024
wchar_t wszMetadata[MAX_METADATA];

int replayStatus = 0;
bool replayReadOnly = false;
int replayUndoCount = 0;
bool frameCounterDisplay = true;
unsigned int replayCurrentFrame;
unsigned int totalFrames = 0;
unsigned int startFrame;
static unsigned int endFrame;
wchar_t currentMovieFilename[MAX_PATH];

static FILE* file = NULL;
static int sizeOffset;

static short prevInputs[0x0100];

static int replayDialog(HWND);
static int recordDialog(HWND);

int recordInput()
{
	struct BurnInputInfo bii;
	memset(&bii, 0, sizeof(bii));

	for (unsigned int i = 0; i < gameInpCount; i++) {
		BurnDrvGetInputInfo(&bii, i);
		if (bii.pVal) {
			if (bii.nType & BIT_GROUP_ANALOG) {
				if (*bii.pShortVal != prevInputs[i]) {
					EncodeBuffer(i);
					EncodeBuffer(*bii.pShortVal >> 8);
					EncodeBuffer(*bii.pShortVal & 0xFF);
					prevInputs[i] = *bii.pShortVal;
				}
			} else {
				if (*bii.pVal != prevInputs[i]) {
					EncodeBuffer(i);
					EncodeBuffer(*bii.pVal);
					prevInputs[i] = *bii.pVal;
				}
			}
		}
	}

	EncodeBuffer(0xFF);

	if (frameCounterDisplay) {
		wchar_t framestring[15];
		swprintf(framestring, L"%d", GetCurrentFrame() - startFrame);
		vidSNewTinyMsg(framestring);
	}

	return 0;
}

int replayInput()
{
	unsigned char n;
	struct BurnInputInfo bii;
	memset(&bii, 0, sizeof(bii));

	// Just to be safe, restore the inputs to the known correct settings
	for (unsigned int i = 0; i < gameInpCount; i++) {
		BurnDrvGetInputInfo(&bii, i);
		if (bii.pVal) {
			if (bii.nType & BIT_GROUP_ANALOG) {
				*bii.pShortVal = prevInputs[i];
			} else {
				*bii.pVal = prevInputs[i];
			}
		}
	}

	// Now read all inputs that need to change from the .fr file
	while ((n = DecodeBuffer()) != 0xFF) {
		BurnDrvGetInputInfo(&bii, n);
		if (bii.pVal) {
			if (bii.nType & BIT_GROUP_ANALOG) {
				*bii.pShortVal = prevInputs[n] = (DecodeBuffer() << 8) | DecodeBuffer();
			} else {
				*bii.pVal = prevInputs[n] = DecodeBuffer();
			}
		} else {
			DecodeBuffer();
		}
	}

	if (frameCounterDisplay) {
		wchar_t framestring[15];
		swprintf(framestring, L"%d / %d", GetCurrentFrame() - startFrame, totalFrames);
		vidSNewTinyMsg(framestring);
	}
	if ((GetCurrentFrame() - startFrame) == (totalFrames - 1))
		runPause = 1;

	if (end_of_buffer) {
		stopReplay();
		return 1;
	}
	return 0;
}

int startRecord()
{
	if (!driverOkay) {
		return 1;
	}

	int ret;
	int bOldPause;

	file = NULL;

	bOldPause = runPause;
	runPause = 1;
	ret = recordDialog(hScrnWnd);
	runPause = bOldPause;

	if (ret == 0) {
		return 1;
	}

	replayReadOnly = false;
	{
		const char szFileHeader[] = "FB1 ";				// File identifier
		file = _wfopen(commonChoice, L"w+b");
		wcscpy(currentMovieFilename, commonChoice);

		ret = 0;
		if (file == NULL) {
			ret = 1;
		} else {
			fwrite(&szFileHeader, 1, 4, file);
			ret = BurnStateSaveEmbed(file, -1, 1);
			if (ret >= 0) {
				const char szChunkHeader[] = "FR1 ";	// Chunk identifier
				int nZero = 0;

				fwrite(&szChunkHeader, 1, 4, file);		// Write chunk identifier

				sizeOffset = ftell(file);

				fwrite(&nZero, 1, 4, file);				// reserve space for chunk size

				fwrite(&nZero, 1, 4, file);				// reserve space for number of frames

				fwrite(&nZero, 1, 4, file);				// undo count
				fwrite(&nZero, 1, 4, file);				// reserved
				fwrite(&nZero, 1, 4, file);				//

				ret = EmbedCompressedFile(file, -1);
			}
		}
	}

	if (ret) {
		if (file) {
			fclose(file);
			file = NULL;
		}

		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_DISK_CREATE));
		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_DISK_REPLAY));
		FBAPopupDisplay(PUF_TYPE_ERROR);
		return 1;
	} else {
		struct BurnInputInfo bii;
		memset(&bii, 0, sizeof(bii));

		replayStatus = 1;					// Set record status
		menuSync(MENUT_FILE);

		startFrame = GetCurrentFrame();
		replayUndoCount = 0;

		// Create a baseline so we can just record the deltas afterwards
		for (unsigned int i = 0; i < gameInpCount; i++) {
			BurnDrvGetInputInfo(&bii, i);
			if (bii.pVal) {
				if (bii.nType & BIT_GROUP_ANALOG) {
					EncodeBuffer(*bii.pShortVal >> 8);
					EncodeBuffer(*bii.pShortVal & 0xFF);
					prevInputs[i] = *bii.pShortVal;
				} else {
					EncodeBuffer(*bii.pVal);
					prevInputs[i] = *bii.pVal;
				}
			} else {
				EncodeBuffer(0);
			}
		}

#ifdef FBA_DEBUG
		dprintf(("*** Recording of file %s started.\n"), commonChoice);
#endif

		return 0;
	}
}

int startReplay(const wchar_t* filename)					// const char* filename = NULL
{
	int ret;
	int bOldPause;

	file = NULL;

	if (filename) {
		wcscpy(commonChoice, filename);
	} else {
		bOldPause = runPause;
		runPause = 1;
		ret = replayDialog(hScrnWnd);
		runPause = bOldPause;

		if (ret == 0) {
			return 1;
		}
	}
	wcscpy(currentMovieFilename, commonChoice);

	// init metadata
	wszMetadata[0] = L'\0';
	{
		const char szFileHeader[] = "FB1 ";					// File identifier
		char ReadHeader[] = "    ";
		file = _wfopen(commonChoice, L"r+b");
		memset(ReadHeader, 0, 4);
		fread(ReadHeader, 1, 4, file);						// Read identifier
		if (memcmp(ReadHeader, szFileHeader, 4)) {			// Not the right file type
			fclose(file);
			file = NULL;
			ret = 2;
		} else {
			// First load the savestate associated with the recording
			ret = BurnStateLoadEmbed(file, -1, 1, &drvInitCallback);
			if (ret == 0) {
				const char szChunkHeader[] = "FR1 ";		// Chunk identifier
				fread(ReadHeader, 1, 4, file);				// Read identifier
				if (memcmp(ReadHeader, szChunkHeader, 4)) {	// Not the right file type
					fclose(file);
					file = NULL;
					ret = 2;
				} else {
					int chunkSize = 0;
					// Open the recording itself
					sizeOffset = ftell(file);				// Save chunk size offset in case the file is re-recorded
					fread(&chunkSize, 1, 0x04, file);		// Read chunk size
					int nChunkPosition = ftell(file);			// For seeking to the metadata
					fread(&endFrame, 1, 4, file);			// Read framecount
					totalFrames = endFrame;
					startFrame = GetCurrentFrame();
					endFrame += startFrame;
					fread(&replayUndoCount, 1, 4, file);
					fseek(file, 0x08, SEEK_CUR);				// Skip rest of header
					int nEmbedPosition = ftell(file);

					// Read metadata
					const char szMetadataHeader[] = "FRM1";
					fseek(file, nChunkPosition + chunkSize, SEEK_SET);
					memset(ReadHeader, 0, 4);
					fread(ReadHeader, 1, 4, file);
					if (memcmp(ReadHeader, szMetadataHeader, 4) == 0) {
						int metaSize;
						fread(&metaSize, 1, 4, file);
						int metaLen = metaSize >> 1;
						if (metaLen >= MAX_METADATA) {
							metaLen = MAX_METADATA-1;
						}
						int i = 0;
						for (i = 0; i < metaLen; ++i) {
							wchar_t c = 0;
							c |= fgetc(file) & 0xff;
							c |= (fgetc(file) & 0xff) << 8;
							wszMetadata[i] = c;
						}
						wszMetadata[i] = L'\0';
					}

					// Seek back to the beginning of compressed data
					fseek(file, nEmbedPosition, SEEK_SET);
					ret = EmbedCompressedFile(file, -1);
				}
			}
		}

		// Describe any possible errors:
		if (ret == 3) {
			FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_DISK_THIS_REPLAY));
			FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_DISK_UNAVAIL));
		} else {
			if (ret == 4) {
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_DISK_THIS_REPLAY));
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_DISK_TOOOLD), APP_TITLE);
			} else {
				if (ret == 5) {
					FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_DISK_THIS_REPLAY));
					FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_DISK_TOONEW), APP_TITLE);
				} else {
					if (ret) {
						FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_DISK_LOAD));
						FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_DISK_REPLAY));
					}
				}
			}
		}

		if (ret) {
			if (file) {
				fclose(file);
				file = NULL;
			}

			FBAPopupDisplay(PUF_TYPE_ERROR);

			return 1;
		}
	}

	replayStatus = 2;					// Set replay status
	menuSync(MENUT_FILE);

	{
		struct BurnInputInfo bii;
		memset(&bii, 0, sizeof(bii));

		LoadCompressedFile();

		// Get the baseline
		for (unsigned int i = 0; i < gameInpCount; i++) {
			BurnDrvGetInputInfo(&bii, i);
			if (bii.pVal) {
				if (bii.nType & BIT_GROUP_ANALOG) {
					*bii.pShortVal = prevInputs[i] = (DecodeBuffer() << 8) | DecodeBuffer();

				} else {
					*bii.pVal = prevInputs[i] = DecodeBuffer();
				}
			} else {
				DecodeBuffer();
			}
		}
	}

#ifdef FBA_DEBUG
	dprintf(("*** Replay of file %s started.\n"), commonChoice);
#endif

	return 0;
}

static void closeRecord()
{
	int frames = GetCurrentFrame() - startFrame;

	WriteCompressedFile();

	fseek(file, 0, SEEK_END);
	int metadataOffset = ftell(file);
	int chunkSize = ftell(file) - 4 - sizeOffset;		// Fill in chunk size and no of recorded frames
	fseek(file, sizeOffset, SEEK_SET);
	fwrite(&chunkSize, 1, 4, file);
	fwrite(&frames, 1, 4, file);
	fwrite(&replayUndoCount, 1, 4, file);

	// NOTE: chunk should be aligned here, since the compressed
	// file code writes 4 bytes at a time

	// write metadata
	size_t metaLen = wcslen(wszMetadata);
	if (metaLen > 0) {
		fseek(file, metadataOffset, SEEK_SET);
		const char szChunkHeader[] = "FRM1";
		fwrite(szChunkHeader, 1, 4, file);
		size_t metaSize = metaLen * 2;
		fwrite(&metaSize, 1, 4, file);
		unsigned char* metabuf = (unsigned char*)malloc(metaSize);
		for (int i = 0; i < metaLen; ++i) {
			metabuf[i*2 + 0] = wszMetadata[i] & 0xff;
			metabuf[i*2 + 1] = (wszMetadata[i] >> 8) & 0xff;
		}
		fwrite(metabuf, 1, metaSize, file);
		free(metabuf);
	}

	fclose(file);
	file = NULL;
}

static void closeReplay()
{
	CloseCompressedFile();

	if (file) {
		fclose(file);
		file = NULL;
	}
}

void stopReplay()
{
	if (replayStatus) {
		if (replayStatus == 1) {
#ifdef FBA_DEBUG
			dprintf((" ** Recording stopped, recorded %d frames.\n"), GetCurrentFrame() - startFrame);
#endif
			closeRecord();
#ifdef FBA_DEBUG
			PrintResult();
#endif
		} else {
#ifdef FBA_DEBUG
			dprintf((" ** Replay stopped, replayed %d frames.\n"), GetCurrentFrame() - startFrame);
#endif
			closeReplay();
		}

		replayStatus = 0;

		if (frameCounterDisplay) {
			frameCounterDisplay = false;
			vidSKillTinyMsg();
			vidSKillTinyOSDMsg();
		}
		menuSync(MENUT_FILE);
	}
}

//#
//#             Input Status Freezing
//#
//##############################################################################

static inline void Write32(unsigned char*& ptr, const unsigned long v)
{
	*ptr++ = (unsigned char)(v&0xff);
	*ptr++ = (unsigned char)((v>>8)&0xff);
	*ptr++ = (unsigned char)((v>>16)&0xff);
	*ptr++ = (unsigned char)((v>>24)&0xff);
}

static inline unsigned long Read32(const unsigned char*& ptr)
{
	unsigned long v;
	v = (unsigned long)(*ptr++);
	v |= (unsigned long)((*ptr++)<<8);
	v |= (unsigned long)((*ptr++)<<16);
	v |= (unsigned long)((*ptr++)<<24);
	return v;
}

static inline void Write16(unsigned char*& ptr, const unsigned short v)
{
	*ptr++ = (unsigned char)(v&0xff);
	*ptr++ = (unsigned char)((v>>8)&0xff);
}

static inline unsigned short Read16(const unsigned char*& ptr)
{
	unsigned short v;
	v = (unsigned short)(*ptr++);
	v |= (unsigned short)((*ptr++)<<8);
	return v;
}

int freezeInput(unsigned char** buf, int* size)
{
	*size = 4 + 2*gameInpCount;
	*buf = (unsigned char*)malloc(*size);
	if (!*buf) {
		return -1;
	}

	unsigned char* ptr=*buf;
	Write32(ptr, gameInpCount);

	for (unsigned int i = 0; i < gameInpCount; i++) {
		Write16(ptr, prevInputs[i]);
	}

	return 0;
}

int unfreezeInput(const unsigned char* buf, int size)
{
	unsigned int n = Read32(buf);
	if (n > 0x100 || (unsigned)size < (4 + 2 * n)) {
		return -1;
	}

	for (unsigned int i = 0; i < n; i++) {
		prevInputs[i] = Read16(buf);
	}

	return 0;
}

//------------------------------------------------------

static void getRecordingPath(wchar_t* szPath)
{
	wchar_t szDrive[MAX_PATH];
	wchar_t szDirectory[MAX_PATH];
	wchar_t filename[MAX_PATH];
	wchar_t szExt[MAX_PATH];
	szDrive[0] = '\0';
	szDirectory[0] = '\0';
	filename[0] = '\0';
	szExt[0] = '\0';
	_wsplitpath(szPath, szDrive, szDirectory, filename, szExt);
	if (szDrive[0] == '\0' && szDirectory[0] == '\0') {
		wchar_t szTmpPath[MAX_PATH];
		wcscpy(szTmpPath, getMiscPath(PATH_RECORDING));
		wcsncpy(szTmpPath + wcslen(szTmpPath), szPath, MAX_PATH - wcslen(szTmpPath));
		szTmpPath[MAX_PATH - 1] = '\0';
		wcscpy(szPath, szTmpPath);
	}
}

static void displayReplayProperties(HWND hDlg, bool clear)
{
	// save status of read only checkbox
	static bool readOnlyStatus = true;
	if (IsWindowEnabled(GetDlgItem(hDlg, IDC_READONLY))) {
		readOnlyStatus = (BST_CHECKED == SendDlgItemMessage(hDlg, IDC_READONLY, BM_GETCHECK, 0, 0));
	}

	// set default values
	SetDlgItemText(hDlg, IDC_LENGTH, L"");
	SetDlgItemText(hDlg, IDC_FRAMES, L"");
	SetDlgItemText(hDlg, IDC_UNDO, L"");
	SetDlgItemText(hDlg, IDC_METADATA, L"");
	EnableWindow(GetDlgItem(hDlg, IDC_READONLY), FALSE);
	SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, BST_UNCHECKED, 0);
	EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
	if (clear) {
		return;
	}

	LONG lCount = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCOUNT, 0, 0);
	LONG lIndex = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCURSEL, 0, 0);
	if (lIndex == CB_ERR) {
		return;
	}

	if (lIndex == lCount - 1) {							// Last item is "Browse..."
		EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);		// Browse is selectable
		return;
	}

	LONG lStringLength = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETLBTEXTLEN, (WPARAM)lIndex, 0);
	if (lStringLength + 1 > MAX_PATH) {
		return;
	}

	SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETLBTEXT, (WPARAM)lIndex, (LPARAM)commonChoice);

	// check relative path
	getRecordingPath(commonChoice);

	const char szFileHeader[] = "FB1 ";					// File identifier
	const char szSavestateHeader[] = "FS1 ";			// Chunk identifier
	const char szRecordingHeader[] = "FR1 ";			// Chunk identifier
	const char szMetadataHeader[] = "FRM1";				// Chunk identifier
	char ReadHeader[4];
	int chunkSize = 0;
	int chunkDataPosition = 0;
	int fileVer = 0;
	int fileMin = 0;
	int t1 = 0, t2 = 0;
	int frames = 0;
	int undoCount = 0;
	wchar_t* local_metadata = NULL;

	FILE* fd = _wfopen(commonChoice, L"rb");
	if (!fd) {
		return;
	}

	if (!fileReadable(commonChoice)) {
		SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, BST_CHECKED, 0);
	} else {
		EnableWindow(GetDlgItem(hDlg, IDC_READONLY), TRUE);
		SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, BST_CHECKED, 0); //read-only by default
	}

	memset(ReadHeader, 0, 4);
	fread(ReadHeader, 1, 4, fd);						// Read identifier
	if (memcmp(ReadHeader, szFileHeader, 4)) {			// Not the right file type
		fclose(fd);
		return;
	}

	memset(ReadHeader, 0, 4);
	fread(ReadHeader, 1, 4, fd);						// Read identifier
	if (memcmp(ReadHeader, szSavestateHeader, 4)) {		// Not the chunk type
		fclose(fd);
		return;
	}

	fread(&chunkSize, 1, 4, fd);
	if (chunkSize <= 0x40) {							// Not big enough
		fclose(fd);
		return;
	}

	chunkDataPosition = ftell(fd);

	fread(&fileVer, 1, 4, fd);							// Version of FB that this file was saved from

	fread(&t1, 1, 4, fd);								// Min version of FB that NV  data will work with
	fread(&t2, 1, 4, fd);								// Min version of FB that All data will work with

	fileMin = t2;										// Replays require a full state

//	if (burnVersion < fileMin) {							// Error - emulator is too old to load this state
//		fclose(fd);
//		return;
//	}

	fseek(fd, chunkDataPosition + chunkSize, SEEK_SET);

	memset(ReadHeader, 0, 4);
	fread(ReadHeader, 1, 4, fd);						// Read identifier
	if (memcmp(ReadHeader, szRecordingHeader, 4)) {		// Not the chunk type
		fclose(fd);
		return;
	}

	chunkSize = 0;
	fread(&chunkSize, 1, 4, fd);
	if (chunkSize <= 0x10) {							// Not big enough
		fclose(fd);
		return;
	}

	chunkDataPosition = ftell(fd);
	fread(&frames, 1, 4, fd);
	fread(&undoCount, 1, 4, fd);

	// read metadata
	fseek(fd, chunkDataPosition + chunkSize, SEEK_SET);
	memset(ReadHeader, 0, 4);
	fread(ReadHeader, 1, 4, fd);						// Read identifier
	if (memcmp(ReadHeader, szMetadataHeader, 4) == 0) {
		chunkSize = 0;
		fread(&chunkSize, 1, 4, fd);
		int metaLen = chunkSize >> 1;
		if (metaLen >= MAX_METADATA) {
			metaLen = MAX_METADATA-1;
		}
		local_metadata = (wchar_t*)malloc((metaLen+1)*sizeof(wchar_t));
		int i = 0;
		for (i = 0; i < metaLen; ++i) {
			wchar_t c = 0;
			c |= fgetc(fd) & 0xff;
			c |= (fgetc(fd) & 0xff) << 8;
			local_metadata[i] = c;
		}
		local_metadata[i] = L'\0';
	}

	// done reading file
	fclose(fd);

	// file exists and is the corrent format,
	// so enable the "Ok" button
	EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);

	// turn frames into a length string
	int burnfps = BurnGetRefreshRate();
	int seconds = (frames * 100 + (burnfps >> 1)) / burnfps;
	int minutes = seconds / 60;
	int hours = seconds / 3600;

	// write strings to dialog
	wchar_t szFramesString[32];
	wchar_t szLengthString[32];
	wchar_t szUndoCountString[32];
	swprintf(szFramesString, L"%d", frames);
	swprintf(szLengthString, L"%02d:%02d:%02d", hours, minutes % 60, seconds % 60);
	swprintf(szUndoCountString, L"%d", undoCount);

	SetDlgItemText(hDlg, IDC_LENGTH, szLengthString);
	SetDlgItemText(hDlg, IDC_FRAMES, szFramesString);
	SetDlgItemText(hDlg, IDC_UNDO, szUndoCountString);
	SetDlgItemTextW(hDlg, IDC_METADATA, local_metadata);
	free(local_metadata);
}

static INT_PTR CALLBACK replayDialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM)
{
	if (msg == WM_INITDIALOG) {
		wchar_t szFindPath[MAX_PATH];
		WIN32_FIND_DATA wfd;
		HANDLE hFind;
		int i = 0;

		swprintf(szFindPath, L"%s*.fr", getMiscPath(PATH_RECORDING));
		SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, BST_CHECKED, 0);

		memset(&wfd, 0, sizeof(WIN32_FIND_DATA));
		if (driverOkay) {
			swprintf(szFindPath, L"%s%hs*.fr", getMiscPath(PATH_RECORDING), BurnDrvGetTextA(DRV_NAME));
		}

		hFind = FindFirstFile(szFindPath, &wfd);
		if (hFind != INVALID_HANDLE_VALUE) {
			do {
				if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
					continue;
				}

				SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_INSERTSTRING, i++, (LPARAM)wfd.cFileName);

			} while (FindNextFile(hFind, &wfd));
			FindClose(hFind);
		}
		SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_SETCURSEL, i-1, 0);
		SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_INSERTSTRING, i++, (LPARAM)L"Browse...");

		if (i > 1) {
			displayReplayProperties(hDlg, false);
		}

		SetFocus(GetDlgItem(hDlg, IDC_CHOOSE_LIST));
		return FALSE;
	}

	if (msg == WM_COMMAND) {
		if (HIWORD(wParam) == CBN_SELCHANGE) {
			LONG lCount = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCOUNT, 0, 0);
			LONG lIndex = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCURSEL, 0, 0);
			if (lIndex != CB_ERR) {
				// Selecting "Browse..." will clear the replay properties display
				displayReplayProperties(hDlg, (lIndex == lCount - 1));
			}
		} else if (HIWORD(wParam) == CBN_CLOSEUP) {
			LONG lCount = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCOUNT, 0, 0);
			LONG lIndex = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCURSEL, 0, 0);
			if (lIndex != CB_ERR) {
				if (lIndex == lCount - 1) {
					// send an OK notification to open the file browser
					SendMessage(hDlg, WM_COMMAND, (WPARAM)IDOK, 0);
				}
			}
		} else {
			int wID = LOWORD(wParam);
			switch (wID) {
				case IDOK: {
					LONG lCount = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCOUNT, 0, 0);
					LONG lIndex = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_GETCURSEL, 0, 0);
					if (lIndex != CB_ERR) {
						if (lIndex == lCount - 1) {
							wchar_t szFilter[1024];
							swprintf(szFilter, localiseStringEx(IDS_DISK_FILE_REPLAY), APP_TITLE);
							memcpy(szFilter + wcslen(szFilter), L" (*.fr)\0*.fr\0\0", 14 * sizeof(wchar_t));

							if (!commonFileDialog(hDlg, false, localiseStringEx(IDS_REPLAY_REPLAY), szFilter, getMiscPath(PATH_RECORDING), L"fr")) {
								LONG lOtherIndex = SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_FINDSTRING, (WPARAM)-1, (LPARAM)commonChoice);
								if (lOtherIndex != CB_ERR) {
									// select already existing string
									SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_SETCURSEL, lOtherIndex, 0);
								} else {
									SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_INSERTSTRING, lIndex, (LPARAM)commonChoice);
									SendDlgItemMessage(hDlg, IDC_CHOOSE_LIST, CB_SETCURSEL, lIndex, 0);
								}
								// restore focus to the dialog
								SetFocus(GetDlgItem(hDlg, IDC_CHOOSE_LIST));
								displayReplayProperties(hDlg, false);
								SendDlgItemMessage(hDlg, IDC_READONLY, BM_SETCHECK, BST_CHECKED, 0);
							}
						} else {
							// get readonly status
							replayReadOnly = false;
							if (BST_CHECKED == SendDlgItemMessage(hDlg, IDC_READONLY, BM_GETCHECK, 0, 0)) {
								replayReadOnly = true;
							}
							EndDialog(hDlg, 1);					// only allow OK if a valid selection was made
						}
					}
					return TRUE;
				}

				case IDCANCEL:
					commonChoice[0] = '\0';
					EndDialog(hDlg, 0);
					return FALSE;
			}
		}
	}

	return FALSE;
}

static int replayDialog(HWND parent)
{
	return localiseDialogBox(IDD_REPLAYINP, parent, (DLGPROC)replayDialogProc);
}

static void verifyRecordingFilename(HWND hDlg)
{
	wchar_t filename[MAX_PATH];
	GetDlgItemText(hDlg, IDC_FILENAME, filename, MAX_PATH);

	// if filename null, or, file exists and is not writeable
	// then disable the dialog controls
	if (filename[0] == '\0' ||
		(fileExists(filename) && !fileReadable(filename))) {
		EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
		EnableWindow(GetDlgItem(hDlg, IDC_METADATA), FALSE);
	} else {
		EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
		EnableWindow(GetDlgItem(hDlg, IDC_METADATA), TRUE);
	}
}

static INT_PTR CALLBACK recordDialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM)
{
	if (msg == WM_INITDIALOG) {
		// come up with a unique name
		wchar_t szPath[MAX_PATH];
		wchar_t filename[MAX_PATH];
		int i = 0;
		swprintf(filename, L"%hs.fr", BurnDrvGetTextA(DRV_NAME));
		wcscpy(szPath, filename);
		while (fileExists(szPath)) {
			swprintf(filename, L"%hs-%d.fr", BurnDrvGetTextA(DRV_NAME), ++i);
			wcscpy(szPath, filename);
		}

		SetDlgItemText(hDlg, IDC_FILENAME, filename);
		SetDlgItemTextW(hDlg, IDC_METADATA, L"");

		verifyRecordingFilename(hDlg);

		SetFocus(GetDlgItem(hDlg, IDC_METADATA));
		return FALSE;
	}

	if (msg == WM_COMMAND) {
		if (HIWORD(wParam) == EN_CHANGE) {
			verifyRecordingFilename(hDlg);
		} else {
			int wID = LOWORD(wParam);
			switch (wID) {
				case IDC_BROWSE: {
					wchar_t szFilter[1024];
					swprintf(szFilter, localiseStringEx(IDS_DISK_FILE_REPLAY), APP_TITLE);
					memcpy(szFilter + wcslen(szFilter), L" (*.fr)\0*.fr\0\0", 14 * sizeof(wchar_t));
					swprintf(commonChoice, L"%hs", BurnDrvGetTextA(DRV_NAME));
					if (!commonFileDialog(hDlg, true, localiseStringEx(IDS_REPLAY_RECORD), szFilter, getMiscPath(PATH_RECORDING), L"fr")) {
						// this should trigger an EN_CHANGE message
						SetDlgItemText(hDlg, IDC_FILENAME, commonChoice);
					}
					return TRUE;
				}
				case IDOK:
					GetDlgItemText(hDlg, IDC_FILENAME, commonChoice, MAX_PATH);
					GetDlgItemTextW(hDlg, IDC_METADATA, wszMetadata, MAX_METADATA);
					wszMetadata[MAX_METADATA-1] = L'\0';

					// create dir if dir doesn't exist
					if (!directoryExists(getMiscPath(PATH_RECORDING))) {
						CreateDirectory(getMiscPath(PATH_RECORDING), NULL);
					}
					// ensure a relative path has the "recordings\" path in prepended to it
					getRecordingPath(commonChoice);

					EndDialog(hDlg, 1);
					return TRUE;
				case IDCANCEL:
					commonChoice[0] = '\0';
					EndDialog(hDlg, 0);
					return FALSE;
			}
		}
	}

	return FALSE;
}

static int recordDialog(HWND parent)
{
	return localiseDialogBox(IDD_RECORDINP, parent, (DLGPROC)recordDialogProc);
}
